package de.jmda.gen.template;

import static de.jmda.gen.LineIndenter.TAB_SINGLE;
import static de.jmda.gen.java.ModifiersUtil.VisibilityModifier.PUBLIC;
import static de.jmda.gen.java.naming.ImportManagerProvider.getImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.newImportManager;
import static de.jmda.gen.java.naming.ImportManagerProvider.restoreImportManager;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import de.jmda.core.util.TypeUtil;
import de.jmda.gen.GeneratorException;
import de.jmda.gen.java.ClassGenerator;
import de.jmda.gen.java.CompilationUnitUtil;
import de.jmda.gen.java.FieldGenerator;
import de.jmda.gen.java.MethodGenerator;
import de.jmda.gen.java.impl.DefaultClassGenerator;
import de.jmda.gen.java.impl.DefaultInstanceFieldGenerator;
import de.jmda.gen.java.impl.DefaultInstanceMethodGenerator;
import de.jmda.gen.java.naming.ImportManager;

public abstract class TemplateUtil
{
	public final static String REGEX_TEMPLATE_PLACEHOLDER_PREFIX = "\\$\\{";
	public final static String REGEX_TEMPLATE_PLACEHOLDER_SUFFIX =    "\\}";

	public final static String REGEX_TEMPLATE_PLACEHOLDER_PREFIX_ESCAPED =
			REGEX_TEMPLATE_PLACEHOLDER_PREFIX.replaceAll("\\\\", "\\\\\\\\");
	public final static String REGEX_TEMPLATE_PLACEHOLDER_SUFFIX_ESCAPED =
			REGEX_TEMPLATE_PLACEHOLDER_SUFFIX.replaceAll("\\\\", "\\\\\\\\");

	public final static String REGEX_TEMPLATE_PLACEHOLDER =
			REGEX_TEMPLATE_PLACEHOLDER_PREFIX +
//          start with a single lower case character
//          |          continue with arbitrary chars containing lower or upper
//          |          | chase chars or numerical chars
//       ---|--  ------|-----
//      |      ||            |
			"([a-z]{1}[a-z|A-Z|0-9]*)" +
			REGEX_TEMPLATE_PLACEHOLDER_SUFFIX;

	private final static Pattern REGEX_TEMPLATE_PLACEHOLDER_PATTERN =
			Pattern.compile(REGEX_TEMPLATE_PLACEHOLDER);

	public static Set<String> findPlaceholders(String string)
	{
		Set<String> result = new HashSet<>();

		Matcher matcher = REGEX_TEMPLATE_PLACEHOLDER_PATTERN.matcher(string);
		
    while (matcher.find())
    {
    	result.add(matcher.group(1));
    }
		
		return result;
	}
	
	public static void generateBinder(
			String template, File rootDirectory, String qualifiedName)
			throws GeneratorException
	{
		// extract type's package and name
		String packagename =
				TypeUtil.getPackageNameFromQualifiedTypeName(qualifiedName);
		String simpleTypename =
				TypeUtil.getSimpleNameFromQualifiedTypeName(qualifiedName);
		
		Set<String> placeholders = new TreeSet<>(findPlaceholders(template));

		// make backup of current import manager
		ImportManager backup = newImportManager(packagename, simpleTypename);

		// create and configure a generator for the Java Bean to be generated
		ClassGenerator cg = new DefaultClassGenerator();

		cg.setPackageName(packagename);
		cg.setVisibility(PUBLIC);
		cg.setClassName(simpleTypename);
		cg.setExtendsClause(AbstractBinder.class);

		for (String placeholder : placeholders)
		{
			// create and configure a generator for the field declaration
			FieldGenerator fg = new DefaultInstanceFieldGenerator();

			fg.setVisibility(PUBLIC);
			fg.setType(String.class.getSimpleName());
			fg.setName(placeholder);
			fg.setLineIndenter(TAB_SINGLE);

			// add field generator to class generator
			cg.addFieldGenerator(fg);
		}
		
		MethodGenerator mg = new DefaultInstanceMethodGenerator();

		ImportManager importManager = getImportManager();

		mg.setVisibility(PUBLIC);
		mg.setType(importManager.useType(Map.class) + "<String, String>");
		mg.setName("getBinding");
		mg.setMethodBody
		(
importManager.useType(Map.class) + "<String, String> result = new " +
importManager.useType(HashMap.class) + "<>();\n\n" +
getBinding(placeholders) + "\n" +
"return result;"
		);
		mg.setLineIndenter(TAB_SINGLE);
		
		// add getter method generator to class generator
		cg.addMethodGenerator(mg);

		// add generator for import statements to class generator
		cg.addImportStatementGenerators(
				getImportManager().getImportStatementGenerators());

		try
    {
			FileUtils.writeStringToFile(
					CompilationUnitUtil.buildFile(
							"src/gen/java", packagename, simpleTypename),
					cg.generate().toString());
    }
    catch (GeneratorException | IOException e)
    {
    	throw new GeneratorException("failure generating binder", e);
    }
		finally
		{
			// restore formally import manager
			restoreImportManager(backup);
		}
	}

	private static String getBinding(Set<String> placeholders)
  {
		StringBuffer result = new StringBuffer();
		
		for (String key : placeholders)
		{
			result.append
			(
"result.put"                                            +
"("                                                     +
		"\""                                                +
				REGEX_TEMPLATE_PLACEHOLDER_PREFIX_ESCAPED + key +
				REGEX_TEMPLATE_PLACEHOLDER_SUFFIX_ESCAPED       +
		"\", " + key                                        +
");\n"
			);
		}

	  return result.toString();
  }
}